﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows.Forms;

namespace Chromium_Updater
{
    public delegate void updateCheck(CheckResult r);
    public delegate void downloadProgressChanged(DownloadProgressChangedEventArgs e);
    public delegate void Download(DownloadResult r);
    public delegate void Install(InstallResult r);

    class CUpdater
    {
        private int latest_revision = 0;
        //
        private WebClient downloader;
        //
        public event updateCheck checkReturn;
        public event downloadProgressChanged progressChanged;
        public event Download downloadReturn;
        public event Install installReturn;

        public CUpdater()
        {
            CULogger.fileCheck();
            //
            downloader = new WebClient();
            downloader.DownloadProgressChanged += new DownloadProgressChangedEventHandler(downloader_DownloadProgressChanged);
            downloader.DownloadFileCompleted += new AsyncCompletedEventHandler(downloader_DownloadFileCompleted);
        }

        #region Update Checking

        public void checkForUpdateAsync()
        {
            new Thread(new ThreadStart(checkForUpdateWorker)).Start();
        }

        private void checkForUpdateWorker()
        {
            try
            {
                latest_revision = Convert.ToInt32(downloader.DownloadString(Settings.Latest_Revision_URL));
                if (latest_revision > Settings.Current_Revision)
                {
                    CULogger.writeLine(CUAction.Check, "Revision: " + latest_revision.ToString() + " available"); checkReturn(CheckResult.UpdateAvailable); return;
                }
                else { CULogger.writeLine(CUAction.Check, "You have the latest revision"); checkReturn(CheckResult.NoUpdateAvailable); return; }
            }
            catch (ArgumentNullException e) { CULogger.writeError(CUAction.Check, e.Message); checkReturn(CheckResult.InvalidURL); return; }
            catch (FormatException e) { CULogger.writeError(CUAction.Check, e.Message); checkReturn(CheckResult.InvalidURL); return; }
            catch (OverflowException e) { CULogger.writeError(CUAction.Check, e.Message); checkReturn(CheckResult.InvalidURL); return; }
            catch (WebException e)
            {
                if (e.Message.Contains("The remote name could not be resolved:")) { CULogger.writeError(CUAction.Check, "No Internet"); checkReturn(CheckResult.NoInternet); return; }
                else { CULogger.writeError(CUAction.Check, e.Message); throw (e); }
            }
        }

        public int latestRevision
        {
            get { return latest_revision; }
        }

        #endregion

        #region Downloading

        public void downloadUpdateAsync()
        {
            if (!Directory.Exists(Settings.Download_Directory)) { Directory.CreateDirectory(Settings.Download_Directory); }
            //
            try
            {
                downloader.DownloadFileAsync(new Uri(Settings.Specific_Revision_URL + latest_revision + "/mini_installer.exe"), Settings.Download_Directory + "\\mini_installer.exe");
            }
            catch (ArgumentNullException e) { CULogger.writeError(CUAction.PreDownload, e.Message); downloadReturn(DownloadResult.InvalidURL); return; }
            catch (InvalidOperationException e) { CULogger.writeError(CUAction.PreDownload, e.Message); downloadReturn(DownloadResult.FileInUse); return; }
        }

        private void downloader_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            progressChanged(e);
        }

        private void downloader_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                if (e.Error.Message.Contains("The remote name could not be resolved:")) { CULogger.writeError(CUAction.Download, "No Internet"); downloadReturn(DownloadResult.NoInternet); }
                else { CULogger.writeError(CUAction.Download, e.Error.Message); }
            }
            else
            {
                CULogger.writeLine(CUAction.Download, "Revison: " + latest_revision.ToString() + " downloaded succesfully");
                downloadReturn(DownloadResult.Success);
            }
        }

        #endregion

        #region Installing

        public void installUpdateAsync()
        {
            new Thread(new ThreadStart(installerWatcher)).Start();
        }

        private void installerWatcher()
        {
            List<string> openChromeProcesses = null;
            if (isChromiumRunning)
            {
                if (MessageBox.Show("Chromium Updater is trying to install revision " + '"' + latest_revision + '"' + " but Chromium or Chrome is running. Would you like Chromium Updater to update it and restart your browser?", "Chromium Updater", MessageBoxButtons.YesNo) == DialogResult.No)
                {
                    CULogger.writeLine(CUAction.Install, "User decided not to close Chromium/Chrome"); installReturn(InstallResult.ChromiumRunning); return;
                }
                openChromeProcesses = closeChromium();
            }
            //
            try
            {
                Process.Start(Settings.Download_Directory + "\\mini_installer.exe").WaitForExit(Settings.Installer_Timeout * 1000);
            }
            catch (Win32Exception e)
            {
                CULogger.writeError(CUAction.Install, e.Message); installReturn(InstallResult.Fail); return;
            }
            //
            Settings.Current_Revision = latest_revision;
            CULogger.writeLine(CUAction.Install, "Revision: " + latest_revision.ToString() + " installed succesfully");
            if (openChromeProcesses != null) { openChromium(openChromeProcesses); }
            installReturn(InstallResult.Success);
        }

        private bool isChromiumRunning
        {
            get
            {
                if (Process.GetProcessesByName("chrome").Length > 0) { return true; }
                else { return false; }
            }
        }

        private List<string> closeChromium()
        {
            List<string> startedFilePaths = new List<string>();
            Process[] chrome_procs = Process.GetProcessesByName("chrome");
            foreach (Process proc in chrome_procs)
            {
                if (!startedFilePaths.Contains(proc.Modules[0].FileName)) { startedFilePaths.Add(proc.Modules[0].FileName); }
                if (!proc.CloseMainWindow())
                {
                    proc.Kill();
                }
            }
            return startedFilePaths;
        }

        private void openChromium(List<string> closedProcesses)
        {
            foreach (string proc in closedProcesses)
            {
                Process.Start(proc);
            }
        }

        #endregion

        #region Misc functions

        public bool isBusy
        {
            get { return downloader.IsBusy; }
        }

        #endregion
    }

    #region Custom Returns

    public enum CheckResult
    {
        UpdateAvailable,
        NoUpdateAvailable,
        InvalidURL,
        NoInternet
    }

    public enum DownloadResult
    {
        Success,
        InvalidURL,
        NoInternet,
        FileInUse
    }

    public enum InstallResult
    {
        Success,
        Fail,
        ChromiumRunning
    }

    #endregion

    #region CUpdater Logging

    #region CUAction

    enum CUAction
    {
        Check,
        PreDownload,
        Download,
        Install
    }

    #endregion

    #region CULogEntry

    public struct CULogEntry
    {
        private DateTime _time;
        private string _action;
        private string _message;

        public CULogEntry(DateTime time_, string action_, string message_)
        {
            _time = time_;
            _action = action_;
            _message = message_;
        }

        public DateTime Time { get { return _time; } }

        public string Action { get { return _action; } }

        public string Message { get { return _message; } }
    }

    #endregion

    #region CULReader

    static class CULReader
    {
        private static int _checks = 0;
        private static int _downloads = 0;
        private static int _installs = 0;
        //
        public static int Checks { get { return _checks; } }
        public static int Downloads { get { return _downloads; } }
        public static int Installs { get { return _installs; } }

        public static List<CULogEntry> loadCULog()
        {
            if (!File.Exists(Settings.CUpdater_Log_Path)) { return null; }
            //
            string[] _entries = File.ReadAllLines(Settings.CUpdater_Log_Path);
            List<CULogEntry> entries = new List<CULogEntry>();
            _checks = 0;
            _downloads = 0;
            _installs = 0;
            for (int i = 3; i < _entries.Length; i++)
            {
                CULogEntry tempEntry = stringToCULogEntry(_entries[i]);
                entries.Add(tempEntry);
                switch (tempEntry.Action)
                {
                    case "Check":
                        _checks++;
                        break;
                    case "Download":
                        _downloads++;
                        break;
                    case "Install":
                        _installs++;
                        break;
                }
            }
            return entries;
        }

        public static string CUActionToString(CUAction action)
        {
            string actionString = "";
            switch (action)
            {
                case CUAction.Check:
                    actionString = "Check";
                    break;
                case CUAction.PreDownload:
                    actionString = "PreDownload";
                    break;
                case CUAction.Download:
                    actionString = "Download";
                    break;
                case CUAction.Install:
                    actionString = "Install";
                    break;
            }
            return actionString;
        }

        public static CULogEntry stringToCULogEntry(string unparsedEntry)
        {
            string[] firstSplit = unparsedEntry.Split('-');
            int tempIndex = firstSplit[1].IndexOf(':');
            string action = firstSplit[1].Substring(0, tempIndex).Trim();
            string message = firstSplit[1].Substring(tempIndex + 2, firstSplit[1].Length - tempIndex - 2).Trim();
            return new CULogEntry(DateTime.Parse(firstSplit[0]).ToLocalTime(), action, message);
        }

        public static string CULogEntryToString(CULogEntry entry)
        {
            return entry.Time.ToLocalTime() + " - " + entry.Action + ": " + entry.Message;
        }
    }

    #endregion

    #region CULogger

    static class CULogger
    {
        public static void fileCheck()
        {
            if (!File.Exists(Settings.CUpdater_Log_Path))
            {
                string[] header = new string[3];
                header[0] = "============================================================";
                header[1] = "======================  CUpdater Log  ======================";
                header[2] = "============================================================";
                File.WriteAllLines(Settings.CUpdater_Log_Path, header);
            }
        }

        public static void clearLog()
        {
            File.Delete(Settings.CUpdater_Log_Path);
            fileCheck();
        }

        public static void writeLine(CUAction action, string line)
        {
            File.AppendAllText(Settings.CUpdater_Log_Path, DateTime.Now.ToLocalTime() + " - " + CULReader.CUActionToString(action) + ": " + line + "\r\n");
        }

        public static void writeError(CUAction action, string error)
        {
            writeLine(action, "Error: " + error);
        }
    }

    #endregion

    #endregion
}
